package com.pig4cloud.pigx.system.util;

import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.i18n.util.MessageUtils;
import com.pig4cloud.pigx.common.data.resolver.ParamResolver;
import com.pig4cloud.pigx.system.api.feign.RippleRemoteSystemService;
import com.pig4cloud.pigx.system.constant.EmailTemplateTypeEnum;
import com.pig4cloud.pigx.system.constant.I18nMessages;
import com.pig4cloud.pigx.system.dto.ArticleInformationDTO;
import com.pig4cloud.pigx.system.vo.EmailInformationVO;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.pig4cloud.pigx.system.constant.I18nMessages.EMAIL_CANNOT_BE_EMPTY;
import static com.pig4cloud.pigx.system.constant.I18nMessages.EMAIL_FORMAT_ERROR;
import static com.pig4cloud.pigx.system.constant.I18nMessages.EMAIL_TEMPLATE_TYPE_DOSE_NOT_EXIST;

/**
 * @Description: 邮件发送工具类
 * @Date: 2022/9/27 10:20
 */
@Component
@RequiredArgsConstructor
public class SendEmailUtil {

    /**
     * RedisKeyEnum
     */
    private final StringRedisTemplate stringRedisTemplate;
    /**
     * ripple-system-biz的Feign
     */
    private final RippleRemoteSystemService rippleRemoteSystemService;


    /**
     * 发送邮件(替换参数)
     *
     * @param titleCode          <${参数名}，参数>  邮件标题替换参数集合
     * @param textContentCode    <${参数名}，参数>  内容邮件替换参数集合
     * @param email              收件人邮箱
     * @param emailInformationVO 邮件内容信息
     * @return
     */
    public R sendEmail(Map<String, String> titleCode, Map<String, String> textContentCode,
                       String email, EmailInformationVO emailInformationVO) {
        // 配置发送邮件的环境属性
        final Properties props = new Properties();
        // 发送邮件协议名称
        props.setProperty("mail.transport.protocol", ParamResolver.getStr("MAIL_TRANSPORT_PROTOCOL", "smtp"));
        // 表示SMTP发送邮件，发送服务器需要进行身份验证
        props.put("mail.smtp.auth", ParamResolver.getStr("MAIL_SMTP_AUTH", "true"));
        // 设置邮件服务器主机名
        props.put("mail.smtp.host", ParamResolver.getStr("MAIL_SMTP_HOST", "smtp.sg.aliyun.com"));
        //smtp登陆的账号、密码 ；需开启smtp登陆
        props.put("mail.user", ParamResolver.getStr("MAIL_USER", ""));
        // 访问SMTP服务时需要提供的密码,不是邮箱登陆密码，一般都有独立smtp的登陆密码
        props.put("mail.password", ParamResolver.getStr("MAIL_PASSWORD", ""));
        // 邮件SMTP时间限制,单位毫秒。这个是有关邮件读取时间长短。默认是无限制。
        props.put("mail.smtp.timeout", 30000);
        // 连接时间限制，单位毫秒。是关于与邮件服务器建立连接的时间长短的。默认是无限制
        props.put("mail.smtp.connectiontimeout", 30000);
        // 邮件发送时间限制，单位毫秒。看关发送邮件时内容上传的时间长短。默认同样是无限制。
        props.put("mail.smtp.writetimeout", 30000);
        // 阿里云25端口默认禁用，使用465 SSL接口
        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.put("mail.smtp.port", "465");
        props.put("mail.smtp.socketFactory.port", "465");

        // 构建授权信息，用于进行SMTP进行身份验证
        Authenticator authenticator = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                // 用户名、密码
                String userName = props.getProperty("mail.user");
                String password = props.getProperty("mail.password");
                return new PasswordAuthentication(userName, password);
            }
        };

        // 使用环境属性和授权信息，创建邮件会话
        Session mailSession = Session.getInstance(props, authenticator);
        // 创建邮件消息
        MimeMessage message = new MimeMessage(mailSession);

        try {
            // 设置发件人
            InternetAddress form = new InternetAddress(props.getProperty("mail.user"),
                    ParamResolver.getStr("MAIL_SENDER"), "UTF-8");
            message.setFrom(form);

            // 设置收件人
            InternetAddress to = new InternetAddress(email);
            message.setRecipient(Message.RecipientType.TO, to);

            // 设置邮件标题
            String emailTitle = emailInformationVO.getTitle();
            if (titleCode != null && !titleCode.isEmpty()) {
                // 替换标题中的参数
                for (String title : titleCode.keySet()) {
                    emailTitle = emailTitle.replace(title,
                            titleCode.get(title) == null ? "" : titleCode.get(title));
                }
            }
            message.setSubject(emailTitle, StandardCharsets.UTF_8.name());
            // 设置邮件的内容体
            String emailTextContent = emailInformationVO.getTextContent();
            if (textContentCode != null && !textContentCode.isEmpty()) {
                // 替换内容中的参数
                for (String textContent : textContentCode.keySet()) {
                    emailTextContent = emailTextContent.replace(textContent,
                            textContentCode.get(textContent) == null ? "" : textContentCode.get(textContent));
                }
            }
            message.setContent(emailTextContent, "text/html;charset=UTF-8");
            // 发送邮件
            Transport.send(message);
        } catch (MessagingException | UnsupportedEncodingException e) {
            new RuntimeException(e);
        }
        return R.ok();
    }

    /**
     * 发送邮件
     *
     * @param email              收件人邮箱
     * @param emailInformationVO 邮件内容信息
     * @return
     */
    public R sendEmail(String email, EmailInformationVO emailInformationVO) {
        return sendEmail(null, null, email, emailInformationVO);
    }

    /**
     * 发送邮件(包含查询邮件模板，替换参数)
     *
     * @param titleCode             <${参数名}，参数>  邮件标题替换参数集合
     * @param textContentCode       <${参数名}，参数>  内容邮件替换参数集合
     * @param email                 收件人邮箱
     * @param articleInformationDTO 邮件模板查询参数（邮件模板语言，数据库文章类型编号）
     * @return
     */
    public R sendEmail(Map<String, String> titleCode, Map<String, String> textContentCode,
                       String email, ArticleInformationDTO articleInformationDTO) {
        // 获取邮箱模板
        R<List<EmailInformationVO>> emailManagementFeign = rippleRemoteSystemService.getEmailManagementFeign(articleInformationDTO, SecurityConstants.FROM_IN);
        //判断数据获取是否成功
        if (emailManagementFeign.getCode() == 1 || (emailManagementFeign.getData()).size() == 0) {
            return R.failed(MessageUtils.get(EMAIL_TEMPLATE_TYPE_DOSE_NOT_EXIST));
        }
        EmailInformationVO emailInformationVO = emailManagementFeign.getData().get(0);
        return sendEmail(titleCode, textContentCode, email, emailInformationVO);
    }

    /**
     * 发送邮件
     *
     * @param titleCode       标题替换参数
     * @param textContentCode 内容替换参数
     * @param email           邮箱
     * @param massage         邮件模板
     * @param language        语言
     * @return {@link R}
     * @since 2022/11/1 19:58
     */
    public R sendEmail(Map<String, String> titleCode,
                       Map<String, String> textContentCode,
                       String email,
                       String massage,
                       String language) {
        //获取文章中的邮件模板
        ArticleInformationDTO articleInformationDTO = new ArticleInformationDTO();
        //邮箱模板类型
        String articleTypeCode = EmailTemplateTypeEnum.findArticleTypeCodeByCode(massage);
        if (Strings.isBlank(articleTypeCode)) {
            //邮箱模板类型不存在
            return R.failed(MessageUtils.get(EMAIL_TEMPLATE_TYPE_DOSE_NOT_EXIST));
        }
        articleInformationDTO.setArticleTypeCode(articleTypeCode);
        //邮箱模板语言，当前语言为空则返回中文
        if (Strings.isBlank(language)) {
            articleInformationDTO.setLanguageId(language);
        } else {
            articleInformationDTO.setLanguageId("zh_CN");
        }

        return sendEmail(titleCode, textContentCode, email, articleInformationDTO);
    }

    /**
     * 邮箱格式验证
     *
     * @param email
     * @return
     */
    public R emailFormatVerification(String email) {
        if (email == null || email.isEmpty()) {
            return R.failed(MessageUtils.get(EMAIL_CANNOT_BE_EMPTY));
        }
        //验证邮箱格式是否正确
        String emailReg = "^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$";
        Pattern pattern = Pattern.compile(emailReg);
        Matcher matcher = pattern.matcher(email);
        if (!matcher.find()) {
            return R.failed(MessageUtils.get(EMAIL_FORMAT_ERROR));
        }
        return R.ok();
    }

    /**
     * 获取验证码
     *
     * @param email   邮箱
     * @param message 消息类型
     * @param expire  过期时间（s）
     * @return {@link String}
     * @since 2022/11/1 19:49
     */
    public String getVerifyCode(String email, String message, Integer expire) {
        // 判断是否重复发送
        String redisKey = getVerifyCodeKey(email, message);
        String existVerifyCode = stringRedisTemplate.opsForValue().get(redisKey);
        if (Strings.isNotBlank(existVerifyCode)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.EMAIL_HAS_BEEN_SUBMITTED));
        }

        // 六位随机数验证码
        String verifyCode = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));

        // 3分钟后过期
        stringRedisTemplate.opsForValue().set(redisKey, verifyCode, expire, TimeUnit.SECONDS);

        return verifyCode;
    }

    /**
     * 获取邮箱验证码在redis中的key
     *
     * @param email   邮箱
     * @param message 消息类型
     * @return {@link String}
     * @since 2022/11/1 19:49
     */
    public String getVerifyCodeKey(String email, String message) {
        return email + " message = " + message;
    }

    /**
     * 校验验证码
     *
     * @param email      邮箱
     * @param message    消息类型
     * @param verifyCode 验证码
     * @since 2022/11/2 11:40
     */
    public void checkVerifyCode(String email, String message, String verifyCode) {
        if (Strings.isBlank(verifyCode)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.VERIFICATION_CODE_IS_INCALID));
        }

        String verifyCodeKey = getVerifyCodeKey(email, message);
        String existVerifyCode = stringRedisTemplate.opsForValue().get(verifyCodeKey);

        // 验证码不存在 || 不相同
        if (Strings.isBlank(existVerifyCode) || !existVerifyCode.equals(verifyCode)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.VERIFICATION_CODE_IS_INCALID));
        }

        // 验证码相同，清除缓存
        clearVerifyCode(verifyCodeKey);
    }

    /**
     * 验证码校验成功后清除缓存
     *
     * @param key key
     * @since 2022/11/2 11:39
     */
    public void clearVerifyCode(String key) {
        stringRedisTemplate.delete(key);
    }
}

